appId = "102115625"  # 企业版
secret = "qomkigedcbaZYYYYYYYYZabcdegikmoq"

appId2 = "102075443"
secret2 = "TdozALWhs4GSeq2ERer4HUhu8Mao2GUj"

appId3 = "102669223"
secret3 = "MsOuQwT0X4b8fDlJrPxV4dClKtT3dDnN"

import asyncio
import aiohttp
import websockets
import requests
import urllib.request
import time
import json
import base64
import os
import logging
import sys

sys.path.append(r"D:\Myprog")

from Panda.Core.Log import logger,ensure_dir
# from Panda.Core.Log import ensure_dir
from Panda.Reply import check_msg
from datetime import datetime

from url2png import create_image_with_link

logger.setLevel(logging.DEBUG)
FilePath = "D:/MyProg/DDbot/File"
ensure_dir(FilePath)


class Token:

    def __init__(self, appid, secret):
        self.appid = appid
        self.secret = secret
        self.expores_time = 0
        self.access_token = ""
        self.header_token = ""

    async def get_access_token(self):
        url = "https://bots.qq.com/app/getAppAccessToken"
        data = {
            "appId": self.appid,
            "clientSecret": self.secret,
        }
        header = {
            "Content-Type": "application/json",
        }
        res = requests.post(url=url, json=data, headers=header)
        if res.status_code != 200:
            raise Exception("Token get failed!")
        elif "access_token" in res.json() and "expires_in" in res.json():
            logger.warning(res.json())
            self.access_token = res.json()["access_token"]
            self.token_string = f"QQBot {self.access_token}"
            self.expores_time = int(res.json()["expires_in"]) + int(time.time())

    async def check_access_token(self):
        if time.time() > self.expores_time - 60 or not self.access_token:
            await self.get_access_token()


class ReceiveMessage:

    def __init__(self, bot, data):
        self._bot = bot
        self.op = data.get("op", 0)
        self.last_seq = data.get("s", 0)
        self.event_id = data.get("t", "")

        self._Data = data.get("d", {})
        # when resumed, it's empty string
        self._Data = {} if not self._Data else self._Data

        self._attachments = self._Data.get("attachments", [])
        if self._attachments:
            self.filename = self._attachments[0].get("filename", "")
            self.url = self._attachments[0].get("url", "").replace("https", "http")
            logger.debug(self.url)
        self.id = self._Data.get("id", "")
        self.text = self._Data.get("content", "")
        self.session_id = self._Data.get("session_id", "")

        self.time = self.Timestr(self._Data.get("timestamp", "")).strtime

        self._author = self.Author(self._Data.get("author", {}))
        self.user_id = self._author.user_id
        self.group_id = self._Data.get("group_id", "")
        self.openid = self.group_id or self.user_id
        self.is_group = True if self.group_id else False

    def download(self):
        if self._attachments:
            res = requests.get(self.url, stream=True)
            with open(f"{FilePath}/{self.filename}", "wb") as f:
                f.write(res.content)

    def __repr__(self):
        dic = self.__dict__
        return str({key: dic[key] for key in dic if not key.startswith("_")})

    class Author:
        def __init__(self, author):
            self.user_id = author.get("id", "")

        def __repr__(self):
            dic = self.__dict__
            return str({key: dic[key] for key in dic if not key.startswith("_")})

    class Timestr:
        def __init__(self, isotime):
            if not isotime:
                self.strtime = ""
            else:
                self.strtime = datetime.fromisoformat(isotime).strftime(
                    "%Y-%m-%d %H:%M:%S"
                )

        def __repr__(self):
            return self.strtime


class QQBot:

    def __init__(self, appid, secret):
        self.token = Token(appid, secret)
        self.loop = asyncio.new_event_loop()
        self.last = 0
        self.can_connect = False
        self.heartbeat_task = None
        self.closed = False
        self.wss = None
        self.session_id = ""
        self.wss_url = ""
        self.funcs = ["group_at_message_create", "c2c_message_create", "resumed"]
        self.session = None

    def run(self):
        asyncio.set_event_loop(self.loop)
        self.loop.run_until_complete(self.start())

    @property
    def data_identify(self) -> str:
        data = {
            "op": 2,
            "d": {
                "token": f"{self.token.token_string}",
                "intents": 1 << 25,
                "shard": [0, 1],
            },
        }
        return json.dumps(data)

    @property
    def data_resume(self):
        data = {
            "op": 6,
            "d": {
                "token": self.token.token_string,
                "session_id": self.session_id,
                "seq": self.last,
            },
        }
        return json.dumps(data)

    @property
    def data_heartbeat(self):
        data = {"op": 1, "d": self.last}
        return json.dumps(data)

    async def heartbeat(self):
        while not self.closed:
            await self.wss.send(self.data_heartbeat)
            await asyncio.sleep(30)

    async def check_msg_op(self, rec: ReceiveMessage) -> bool:
        op = rec.op
        if op == 10:
            logger.debug("Heartbeat time confirmed!")
            await self.token.check_access_token()
            if self.session_id and self.can_connect:
                logger.critical(self.data_resume)
                await self.wss.send(self.data_resume)
            else:
                logger.critical(self.data_identify)
                await self.wss.send(self.data_identify)
            return True
        elif op == 0:
            logger.debug("Received new message!")
            if "session_id" in rec._Data:
                self.session_id = rec.session_id
                logger.warning(f"Session ID: {self.session_id}")
            self.last = rec.last_seq
        elif op == 7:
            logger.debug("Disconnected from gateway.")
            self.closed = True
            self.can_connect = True
            return True
        elif op == 11:
            return True
        else:
            logger.critical(f"Unknown opcode: {op}")
        return False

    def time_format(self, t):
        return time.strftime("%H:%M:%S", time.gmtime(t / 1000))

    async def get_wss_url(self):
        await self.token.check_access_token()
        self.headers = {
            "Authorization": f"{self.token.token_string}",
            "X-Union-Appid": f"{self.token.appid}",
        }

        api_url = "https://api.sgroup.qq.com/gateway/bot"
        api_dic = await self.make_request("GET", api_url)
        if "url" not in api_dic:
            raise Exception(f"{api_dic["message"]}")
        if "session_start_limit" in api_dic:
            limit = api_dic["session_start_limit"]
            logger.error(
                f"\nConnect remaining: {limit['remaining']}\n"
                f"Reset after time : {self.time_format(limit['reset_after'])}"
            )
        return api_dic["url"]

    async def start(self):
        while True:
            try:
                self.wss_url = await self.get_wss_url()
                await asyncio.sleep(10)
                async with websockets.connect(self.wss_url) as wss:
                    self.wss = wss
                    self.closed = False
                    logger.critical("Connected to gateway.")
                    while not self.closed:
                        res = await self.wss.recv()
                        res = json.loads(res)
                        logger.info(f"Received: {res}")

                        rec = ReceiveMessage(self, res)
                        if await self.check_msg_op(rec):
                            continue

                        if not self.heartbeat_task:
                            self.heartbeat_task = self.loop.create_task(
                                self.heartbeat(), name="heartbeat"
                            )

                        event = rec.event_id.lower()
                        if event in self.funcs:
                            function = getattr(self, event)
                            self.loop.create_task(function(rec))
            except Exception as ex:
                logger.error(f"Error: {ex.args}")
                await asyncio.sleep(30)

    async def c2c_message_create(self, payload: ReceiveMessage):
        logger.debug(f"C2C Message Create: {payload}")
        url = f"https://api.sgroup.qq.com/v2/users/{payload.openid}/messages"
        if payload._attachments:
            payload.download()

        data = await self.check_reply(payload)
        if data.get("content", True):
            logger.debug(data.get("content", True))
            return await self.make_request("POST", url, data)

    async def group_at_message_create(self, payload: ReceiveMessage):
        logger.debug(f"GRP Message Create: {payload}")
        url = f"https://api.sgroup.qq.com/v2/groups/{payload.openid}/messages"
        data = await self.check_reply(payload)
        if data.get("content", True):
            return await self.make_request("POST", url, data)

    async def resumed(self, payload: ReceiveMessage):
        logger.error(f"Resumed successfully!")

    async def make_request(self, method, url, data=None):
        if not self.session or self.session.closed:
            self.session = aiohttp.ClientSession()
        try:
            await self.token.check_access_token()
            self.headers = {
                "Authorization": f"{self.token.token_string}",
                "X-Union-Appid": f"{self.token.appid}",
            }
            async with self.session.request(
                method, url, headers=self.headers, json=data
            ) as response:
                res = await response.json()
                logger.debug(
                    f"\n\t{method:<4}: {url}\n\tHEAD: {self.headers}\n\tDATA: {data}\n\tRESP: {res}"
                )
                return res
        except aiohttp.client_exceptions.ClientConnectorError as e:
            logger.error(e.args)

    async def check_reply(self, payload: ReceiveMessage):
        content = check_msg(payload, self, True, only_ret_txt=True)
        if "http" in content:
            question = payload.text
            create_image_with_link(question, content)
            content = r"D:\Myprog\DDbot\File\URL.png"
            logger.debug(f"{question}: {content}")

        if os.path.exists(content):
            media = await self.upload_base64file(payload, content)
            data = {
                "msg_type": 7,
                "msg_id": payload.id,
                "media": media,
            }
        else:
            data = {
                "content": content,
                "msg_type": 0,
                "msg_id": payload.id,
            }
        return data

    async def upload_base64file(
        self,
        payload: ReceiveMessage,
        file_path: str,
        srv_send_msg: bool = False,
    ):
        """
        上传图片
        Args:
        srv_send_msg (bool): 设置 true 会直接发送消息到目标端，且会占用主动消息频次
        """
        openid = payload.openid
        is_group = payload.is_group
        if file_path.endswith((".png", ".jpg", ".gif")):
            file_type = 1
        elif file_path.endswith((".mp4", ".mov")):
            file_type = 2
        elif file_path.endswith((".silk", ".wav")):
            file_type = 3
        else:
            file_type = 4

        url_type = "groups" if is_group else "users"
        url = f"https://api.sgroup.qq.com/v2/{url_type}/{openid}/files"
        file_data = base64.b64encode(open(file_path, "rb").read()).decode()

        data = {
            "openid": openid,
            "file_type": file_type,
            "file_data": file_data,
            "srv_send_msg": srv_send_msg,
        }
        res = await self.make_request("POST", url, data)
        logger.error(res)
        res = res if "file_info" in res else {}
        return res


if __name__ == "__main__":
    bot = QQBot(appId3, secret3)
    bot.run()
